#include "enginefactory.h"
#include "grantlee_paths.h"
#include "grantlee_templates.h"
#include "grantlee/templateloader.h"

#include <functional>
#include <QMessageBox>
#include <QDebug>

class NoEscapeOutputStream : public Grantlee::OutputStream
{
public:
    NoEscapeOutputStream() : Grantlee::OutputStream() {}

    NoEscapeOutputStream(QTextStream *stream) : OutputStream(stream) {}

    virtual QSharedPointer<Grantlee::OutputStream> clone() const
    {
        return QSharedPointer<Grantlee::OutputStream>(new NoEscapeOutputStream);
    }

    virtual QString escape(const QString &input) const { return input; }
};

const QString BaseVisitor::baseVisitorClassName = "AstVisitor";
const QString BaseVisitor::baseVisitorHeaderName = "AstVisitor.h";

const QString SpecificTypeVisitor::specificTypeVisitorHeaderName = "visitor.h";
const QString SpecificTypeVisitor::specificTypeVisitorImplName = "visitor.cpp";

template <typename T>
auto literateOverRuleElementsCollection(std::shared_ptr<RuleCollection> visibleRuleCollections, const T &traits)
{
    QStringList list;
    if (visibleRuleCollections)
        for (auto ll = visibleRuleCollections->begin(); ll != visibleRuleCollections->end(); ++ll)
            list += traits(ll);
    return list;
}

QStringList BaseVisitor::visibleContextTypeNames() const
{
    return literateOverRuleElementsCollection(visibleRuleCollections, [](const decltype(visibleRuleCollections->begin()) &ll)
                                              { return QString::fromStdString(ll->second->ruleContextName); });
}

QStringList BaseVisitor::visibleRuleNames() const
{
    return literateOverRuleElementsCollection(visibleRuleCollections, [](const decltype(visibleRuleCollections->begin()) &ll)
                                              { return QString::fromStdString(ll->first); });
}

QStringList BaseVisitor::inVisibleRuleNames() const
{
    QStringList list;
    if (invisibleRuleCollections)
        for (auto ll = invisibleRuleCollections->begin(); ll != invisibleRuleCollections->end(); ++ll)
            list += QString::fromStdString(ll->second->ruleName);
    return list;
}

EngineFactory::EngineFactory()
{
    engine_.reset(new Grantlee::Engine(nullptr));
    engine_->setPluginPaths(QStringList() << GRANTLEE_PLUGIN_PATH
                                          << ":/plugins");
    engine_->addDefaultLibrary("grantlee_scriptabletags");
    engine_->setSmartTrimEnabled(true);

    loader_.reset(new Grantlee::FileSystemTemplateLoader);
    loader_->setTemplateDirs(QStringList() << ":/templates");
    engine_->addTemplateLoader(loader_);
    engine_->addDefaultLibrary("custom_tags");
    loader_->setTheme("cpp");
}

EngineFactory::~EngineFactory()
{
}

QString EngineFactory::genCode(const BaseVisitor &baseVisitor)
{
    Grantlee::Template headerTemplate = engine_->loadByName(BaseVisitor::baseVisitorHeaderName);
    if (headerTemplate->error())
    {
        QMessageBox::warning(nullptr, "Error : ", headerTemplate->errorString());
        return "";
    }
    Grantlee::Context c;
    QVariantList contextNames;

    for (const auto &contextName : baseVisitor.visibleContextTypeNames())
        contextNames << contextName;
    /*     for (auto ll = baseVisitor.invisibleRuleCollections->begin(); ll != baseVisitor.invisibleRuleCollections->end(); ++ll)
            contextNames << QString::fromStdString(ll->second->ruleContextName); */
    c.insert("contextNames", QVariant(contextNames));

    QVariantList dispatcherHash;
    for (const auto &rule : baseVisitor.visibleRuleNames())
        dispatcherHash << rule;
/*     for (const auto &invisibleRule : baseVisitor.inVisibleRuleNames())
        dispatcherHash << invisibleRule; */
    c.insert("allRuleNames", QVariant(dispatcherHash));

    QString output;
    QTextStream textStream(&output);
    NoEscapeOutputStream stream(&textStream);
    headerTemplate->render(&stream, &c);
    if (headerTemplate->error())
    {
        QMessageBox::warning(nullptr, "Error : ", headerTemplate->errorString());
        return "";
    }
    return output;
}

StringPair EngineFactory::genCode(const SpecificTypeVisitor &baseVisitor)
{
    StringPair output;
    {
        Grantlee::Template headerTemplate = engine_->loadByName(SpecificTypeVisitor::specificTypeVisitorHeaderName);
        if (headerTemplate->error())
        {
            QMessageBox::warning(nullptr, "Error : ", headerTemplate->errorString());
            return output;
        }
        Grantlee::Context c;
        c.insert("className", QVariant(baseVisitor.className));
        c.insert("returnType", QVariant(baseVisitor.returnType));
        c.insert("baseType", QVariant(baseVisitor.baseType));
        QVariantList contextNames;
        for (const auto &str : baseVisitor.visibleContextTypeNames())
            contextNames << str;
        /*         for (auto ll = baseVisitor.invisibleRuleCollections->begin(); ll != baseVisitor.invisibleRuleCollections->end(); ++ll)
                    contextNames << QString::fromStdString(ll->second->ruleContextName); */
        c.insert("contextNames", contextNames);

        QTextStream textStream(&output.first);
        NoEscapeOutputStream stream(&textStream);
        headerTemplate->render(&stream, &c);
        if (headerTemplate->error())
        {
            QMessageBox::warning(nullptr, "Error : ", headerTemplate->errorString());
            return output;
        }
    }
    {
        Grantlee::Template headerTemplate = engine_->loadByName(SpecificTypeVisitor::specificTypeVisitorImplName);
        if (headerTemplate->error())
        {
            QMessageBox::warning(nullptr, "Error : ", headerTemplate->errorString());
            return output;
        }
        Grantlee::Context c;
        c.insert("StrToCut", '_');
        c.insert("className", QVariant(baseVisitor.className));
        c.insert("returnType", QVariant(baseVisitor.returnType));
        QVariantList elementsCollection;

        auto insertLocalsForRule = [](QVariantHash &hash, const BaseRuleInfor &info)
        {
            QVariantList locals;
            for (const auto &str : info.locals)
                locals << QString::fromStdString(str);
            hash.insert("locals", locals);
        };

        for (auto ll = baseVisitor.visibleRuleCollections->begin(); ll != baseVisitor.visibleRuleCollections->end(); ++ll)
        {
            QVariantHash ruleCollection;
            auto elements = baseVisitor.ruleElements(QString::fromStdString(ll->second->ruleName), RuleKind::Labeled);
            QVariantList tempElements;
            for (const auto &eleStr : elements)
                tempElements << eleStr;
            ruleCollection.insert("contextName", QVariant(QString::fromStdString(ll->second->ruleContextName)));
            ruleCollection.insert("ruleName", QVariant(QString::fromStdString(ll->second->ruleName)));
            ruleCollection.insert("elements", tempElements);
            ruleCollection.insert("hasElement", tempElements.size() ? true : false);
            ruleCollection.insert("hasLocals", ll->second->locals.size() ? true : false);
            ruleCollection.insert("isAnonymousNode", (!tempElements.size() && (!ll->second->locals.size())));
            insertLocalsForRule(ruleCollection, *ll->second);
            elementsCollection << ruleCollection;
        }
        /*         for (auto ll = baseVisitor.invisibleRuleCollections->begin(); ll != baseVisitor.invisibleRuleCollections->end(); ++ll)
                {
                    QVariantHash ruleCollection;
                    auto elements = baseVisitor.ruleElements(QString::fromStdString(ll->second->ruleName),RuleKind::NonLabeled);
                    QVariantList tempElements;
                    for (const auto &eleStr : elements)
                        tempElements << eleStr;
                    ruleCollection.insert("contextName", QVariant(QString::fromStdString(ll->second->ruleContextName)));
                    ruleCollection.insert("ruleName", QVariant(QString::fromStdString(ll->second->ruleName)));
                    ruleCollection.insert("elements", tempElements);
                    ruleCollection.insert("hasElement", tempElements.size() ? true : false);
                    insertLocalsForRule(ruleCollection, *ll->second);
                    elementsCollection << ruleCollection;
                } */

        c.insert("elementsCollection", elementsCollection);
        QTextStream textStream(&output.second);
        NoEscapeOutputStream stream(&textStream);
        headerTemplate->render(&stream, &c);
        if (headerTemplate->error())
        {
            QMessageBox::warning(nullptr, "Error : ", headerTemplate->errorString());
            return output;
        }
    }
    return output;
}
template <typename T>
auto tryFindInCollection(T &cc, const std::string &name)
{
    QStringList res;
    auto matchedOne = cc.find(name);
    if (matchedOne != cc.end())
        for (const auto &str : matchedOne->second->ruleElementNames)
            res += QString::fromStdString(str);
    return res;
}

QStringList SpecificTypeVisitor::ruleElements(const QString &ruleName, RuleKind kind) const
{
    auto str = ruleName.toStdString();
    return RuleKind::Labeled == kind ? tryFindInCollection(*visibleRuleCollections, str) : tryFindInCollection(*invisibleRuleCollections, str);
}
